namespace ScrumSprintMonitor.Samples.Snapshooter
{
    #region #using Directives

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Threading;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;

    using Extensibility;
    using Extensibility.Events;

    using Infrastructure;

    using Microsoft.Practices.Composite.Events;

    #endregion

    [Export(typeof(ScrumSprintMonitorAddIn))]
    [AddIn(Name = "Snapshooter")]
    public class SnapshooterAddIn : ScrumSprintMonitorAddIn
    {
        private readonly List<Window> capturedWindowsList = new List<Window>();
        private IAddInHost<SnapshooterAddIn> addInHost;
        private ILogger<SnapshooterAddIn> logger;
        private SynchronizationContext syncContext;
        private Timer timer;

        #region Overrides of ScrumSprintMonitorAddIn

        public override void Load()
        {
            this.logger = ServiceLocator.GetInstance<ILogger<SnapshooterAddIn>>();

            this.syncContext = SynchronizationContext.Current;
            this.timer = new Timer(OnTimer, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(10));

            var eventAggregator = ServiceLocator.GetInstance<IEventAggregator>();
            eventAggregator.GetEvent<MonitorWindowShownEvent>().Subscribe(OnMonitorWindowShown);
        }

        public override void Unload()
        {
            this.timer.Dispose();
        }

        #endregion

        private void OnMonitorWindowShown(Window monitorWindow)
        {
            this.capturedWindowsList.Add(monitorWindow);
        }

        private void OnTimer(object state)
        {
            this.syncContext.Post(RenderToPng, state);
        }

        private void RenderToPng(object state)
        {
            if (!this.capturedWindowsList.Any())
            {
                this.logger.Debug("No monitor windows registered to capture.", this.capturedWindowsList.Count);

                return;
            }

            this.logger.Debug("Capturing {0} monitor windows...", this.capturedWindowsList.Count);

            var bitmapSize = new Size(this.capturedWindowsList.Sum(w => w.ActualWidth), this.capturedWindowsList.Max(w => w.ActualHeight));
            var bmp = new WriteableBitmap((int) bitmapSize.Width, (int) bitmapSize.Height, 96, 96, PixelFormats.Pbgra32, null);
            var maxMonitorSize = new Size(this.capturedWindowsList.Max(w => w.ActualWidth), this.capturedWindowsList.Max(w => w.ActualHeight));
            var sourcePixelsArray = new byte[4 * (uint) (maxMonitorSize.Width * maxMonitorSize.Height)];
            var sourceStride = (int) ((maxMonitorSize.Width * 32 + 7) / 8);

            int x = 0;
            foreach (var monitorWindow in this.capturedWindowsList)
            {
                var monitorBmp = new RenderTargetBitmap((int) monitorWindow.ActualWidth, (int) monitorWindow.ActualHeight, 96, 96,
                                                        PixelFormats.Pbgra32);
                monitorBmp.Render(monitorWindow);

                monitorBmp.CopyPixels(sourcePixelsArray, sourceStride, 0);
                bmp.WritePixels(new Int32Rect(x, 0, monitorBmp.PixelWidth, monitorBmp.PixelHeight), sourcePixelsArray, sourceStride, 0);

                x += monitorBmp.PixelWidth;
            }

            var png = new PngBitmapEncoder();
            string filePath = GetNextBitmapFilePath();

            png.Frames.Add(BitmapFrame.Create(bmp));

            using (Stream stm = File.Create(filePath))
            {
                png.Save(stm);

                this.logger.Debug("Captured monitor windows to {0}.", filePath);
            }
        }

        private string GetNextBitmapFilePath()
        {
            if (this.addInHost == null)
            {
                this.addInHost = ServiceLocator.GetInstance<IAddInHost<SnapshooterAddIn>>();
            }

            string fileName = Path.Combine(this.addInHost.GetOutputDirectory(), string.Format("Capture_{0:s}.png", DateTime.Now).Replace(':', '_'));
            Debug.Assert(!File.Exists(fileName));
            return fileName;
        }
    }
}